<!doctype html>
<html lang="zh-CN">

<head>
  <meta charset="utf-8" /><script src="/dist/index.js"></script>
  <meta name="viewport" content="width=device-width,initial-scale=1" />
  <title>CSV 轻量预览工具（自动预览 + 列宽自适应 + 独立滚动）</title>
  <style>
    :root {
      --bg: #0b1020;
      --panel: #121932;
      --card: #0f1530;
      --text: #e7ecf7;
      --muted: #a9b3c7;
      --accent: #5b8cff;
      --accent-2: #7ad1ff;
      --border: rgba(255, 255, 255, .08);
      --table-grid: rgba(255, 255, 255, .06);
      --chip: #1a2347;
    }

    body {
      margin: 0;
      background: var(--bg);
      color: var(--text);
      font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", Helvetica, Arial
    }

    /* === 布局：左右两栏独立滚动（右侧不拉伸左侧） === */
    .shell {
      display: grid;
      grid-template-rows: auto 1fr;
      gap: 14px;
      height: 100vh;
      overflow: hidden;
      padding: 18px;
      box-sizing: border-box
    }

    header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      background: var(--panel);
      border: 1px solid var(--border);
      border-radius: 14px;
      padding: 2px 14px
    }

    .panel {
      display: grid;
      grid-template-columns: 360px 1fr;
      gap: 14px;
      min-height: 0
    }

    .left,
    .right {
      background: var(--panel);
      border: 1px solid var(--border);
      border-radius: 14px;
      min-height: 0;
      overflow: auto
    }

    .left {
      display: grid;
      grid-template-rows: auto 1fr auto
    }

    .left .title,
    .right .title {
      padding: 12px 14px;
      border-bottom: 1px solid var(--border);
      font-weight: 600
    }

    .controls {
      padding: 12px 14px;
      border-bottom: 1px solid var(--border)
    }

    .grid {
      display: grid;
      grid-template-columns: repeat(4, minmax(0, 1fr));
      gap: 10px
    }

    label {
      font-size: 12px;
      color: var(--muted)
    }

    select,
    input[type="text"],
    textarea {
      width: 100%;
      border: 1px solid var(--border);
      background: var(--card);
      color: var(--text);
      border-radius: 10px;
      padding: 8px 10px;
      font-size: 13px
    }

    textarea#csv {
      resize: vertical;
      max-height: 240px;
      min-height: 160px;
      line-height: 1.45;
      font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace
    }

    .drop {
      margin: 12px 14px;
      border: 1px dashed var(--border);
      border-radius: 10px;
      padding: 14px;
      text-align: center;
      color: var(--muted);
        display: none;
    }

    .drop.drag {
      border-color: var(--accent);
      color: var(--accent)
    }

    .actions {
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding: 12px 14px;
      border-top: 1px solid var(--border)
    }

    .btn {
      appearance: none;
      border: 1px solid var(--border);
      background: transparent;
      color: var(--text);
      padding: 8px 12px;
      border-radius: 10px;
      cursor: pointer;
      font-size: 13px
    }

    .btn.primary {
      background: linear-gradient(135deg, var(--accent), var(--accent-2));
      border: none;
      color: #fff
    }

    .chip {
      padding: 6px 10px;
      border-radius: 999px;
      background: var(--chip);
      border: 1px solid var(--border);
      font-size: 12px
    }

    /* === 右侧预览：横向/纵向滚动、表头吸顶 === */
    .right {
      display: grid;
      grid-template-rows: auto 1fr auto
    }

    .right .title {
      display: flex;
      justify-content: space-between;
      align-items: center
    }

    #tableWrap {
      min-height: 0;
      height: 100%;
      overflow: auto
    }

    /* 横竖滚动都在这里发生 */
    table {
      border-collapse: separate;
      border-spacing: 0;
      width: max-content;
      min-width: 100%;
      font-size: 13px;
      table-layout: fixed
    }

    thead th {
      position: sticky;
      top: 0;
      background: linear-gradient(180deg, var(--panel), var(--card));
      z-index: 1
    }

    th,
    td {
      border-right: 1px solid var(--table-grid);
      border-bottom: 1px solid var(--table-grid);
      padding: 8px 10px;
      text-align: left;
      vertical-align: top;
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis
    }

    th:first-child,
    td:first-child {
      border-left: 1px solid var(--table-grid)
    }

    thead tr:first-child th {
      border-top: 1px solid var(--table-grid)
    }

    tr:nth-child(even) td {
      background: rgba(255, 255, 255, .02)
    }

    tr:hover td {
      background: rgba(90, 140, 255, .08)
    }

    footer {
      padding: 10px 16px;
      color: var(--muted);
      text-align: center
    }

    #text-large-btn, #text-min-btn {
        color: #ffffff;
        background: #535bf2;
        border-radius: 6px;
        cursor: pointer;
    }

    #text-min-btn {
        display: none;
    }

    .top-large-csv-text {
        position: fixed;
        left: 0;
        top: 0;
        width: 99vw;
        padding: 0 !important;
        height: 100vh !important;
        z-index: 9999;
    }

    .top-large-csv-text #csv {
        height: 94vh;
        width: 98vw;
        max-height: 100vh;
    }

    @media (max-width:1100px) {
      .panel {
        grid-template-columns: 1fr
      }
    }
  </style>
</head>

<body>
  <div class="shell">
    <header>
      <div>CSV 轻量预览工具 · <span class="chip" id="status">已就绪</span></div>
      <div style="display:flex;gap:10px;align-items:center">
        <a class="btn primary" id="downloadBtn" download="preview.csv">⬇️ 下载 CSV</a>
        <button class="btn" id="clearBtn">🧹 清空</button>
      </div>
    </header>

    <section class="panel">
      <aside class="left">
        <div class="title">输入（粘贴 / 拖拽 / 选择文件）</div>
        <div class="controls">
          <div class="grid">
            <div>
              <label>分隔符</label>
              <select id="delimiter">
                <option value="auto">自动</option>
                <option value=",">逗号 ,</option>
                <option value="\t">Tab</option>
                <option value=";">分号 ;</option>
                <option value="|">竖线 |</option>
              </select>
            </div>
            <div>
              <label>引号字符</label>
              <input id="quote" type="text" maxlength="1" placeholder='默认 "'>
            </div>
            <div>
              <label>转义字符</label>
              <input id="escape" type="text" maxlength="1" placeholder='默认 "'>
            </div>
            <div>
              <label>表头</label>
              <select id="headerRow">
                  <option value="true">第一行是表头</option>
                <option value="false">无表头</option>
              </select>
            </div>
            <div>
              <label>忽略空行</label>
              <select id="skipEmpty">
                <option value="true">是</option>
                <option value="false">否</option>
              </select>
            </div>
            <div>
              <label>去除两端空白</label>
              <select id="trim">
                <option value="true">是</option>
                <option value="false">否</option>
              </select>
            </div>
            <div>
              <label>注释前缀（可选）</label>
              <input id="comment" type="text" maxlength="1" placeholder="#">
            </div>
            <div>
              <label>&nbsp;</label>
              <input type="file" id="file" accept=".csv,text/csv,text/plain" />
            </div>
          </div>
        </div>
        <button id="text-large-btn">放大</button>
        <div class="drop" id="drop"></div>
        <div style="padding:12px 14px;" id="text-input-area-csv" class="">
            <button id="text-min-btn">缩小</button>
          <textarea id="csv" placeholder="在此粘贴 CSV 字符串……"></textarea>
        </div>
        <div class="actions">
          <small class="chip" id="meta">0 行 · 0 列</small>
          <small class="chip" id="time">解析 0 ms</small>
        </div>
      </aside>

      <main class="right">
        <div class="title">预览 <span></span></div>
        <div id="tableWrap">
          <div style="padding:16px;color:var(--muted)">暂无数据，粘贴或上传后自动预览</div>
        </div>
      </main>
    </section>
  </div>

  <script src="./papaparse.js"></script>
  <script>
    (function () {
      const $ = id => document.getElementById(id);
      const csvEl = $('csv'), fileEl = $('file'), drop = $('drop');
      const status = $('status'), meta = $('meta'), time = $('time'), tableWrap = $('tableWrap'), downloadBtn = $('downloadBtn'), clearBtn = $('clearBtn');

      const opt = {
        delimiter: $('delimiter'), quote: $('quote'), escape: $('escape'),
        headerRow: $('headerRow'), skipEmpty: $('skipEmpty'), trim: $('trim'), comment: $('comment')
      };

      function debounce(fn, wait) { let t; return (...a) => { clearTimeout(t); t = setTimeout(() => fn(...a), wait); }; }

      // ========== 自动预览 ==========
      const doPreview = debounce(() => {
        const text = csvEl.value;
        if (!text.trim()) {
          tableWrap.innerHTML = '<div style="padding:16px;color:var(--muted)">暂无数据，粘贴或上传后自动预览</div>';
          meta.textContent = '0 行 · 0 列'; time.textContent = '解析 0 ms';
          status.textContent = '等待输入…'; return;
        }
        const t0 = performance.now();
        const config = {
          header: opt.headerRow.value === 'true',
          skipEmptyLines: opt.skipEmpty.value === 'true',
          delimiter: opt.delimiter.value === 'auto' ? undefined : opt.delimiter.value,
          quoteChar: opt.quote.value || '"', escapeChar: opt.escape.value || '"',
          transformHeader: h => opt.trim.value === 'true' ? h.trim() : h,
          beforeFirstChunk: ck => opt.trim.value === 'true' ? ck.replace(/[\t ]+$/gm, '') : ck,
          comments: opt.comment.value || false
        };
        const out = Papa.parse(text, config);
        const t1 = performance.now();

        let rows = [], isHeader = config.header;
        if (isHeader) {
          rows = [out.meta.fields, ...out.data.map(Object.values)];
        } else {
          rows = out.data;
        }
        renderTable(rows, isHeader);
        meta.textContent = `${rows.length} 行 · ${(rows[0] || []).length} 列`;
        time.textContent = `解析 ${Math.round(t1 - t0)} ms`;
        status.textContent = '预览完成';

        // 下载链接
        const blob = new Blob([text], { type: 'text/csv;charset=utf-8;' });
        downloadBtn.href = URL.createObjectURL(blob);
      }, 250);

      // 输入/粘贴自动预览
      csvEl.addEventListener('input', doPreview);

      // 上传即预览
      fileEl.addEventListener('change', e => {
        const f = e.target.files?.[0]; if (!f) return;
        readFile(f);
      });

      function readFile(f) {
        const reader = new FileReader();
        reader.onload = () => { csvEl.value = reader.result; status.textContent = `已载入：${f.name}`; doPreview(); };
        reader.readAsText(f);
      }

      // 拖拽即预览
      ;['dragenter', 'dragover'].forEach(ev => drop.addEventListener(ev, e => { e.preventDefault(); drop.classList.add('drag'); }));
      ;['dragleave', 'drop'].forEach(ev => drop.addEventListener(ev, e => { e.preventDefault(); drop.classList.remove('drag'); }));
      drop.addEventListener('drop', e => {
        const f = e.dataTransfer.files?.[0]; if (!f) return;
        const reader = new FileReader();
        reader.onload = () => { csvEl.value = reader.result; status.textContent = `已载入：${f.name}`; doPreview(); };
        reader.readAsText(f);
      });

      // 改解析选项时也自动预览
      ['delimiter', 'quote', 'escape', 'headerRow', 'skipEmpty', 'trim', 'comment'].forEach(id => {
        const el = $(id); el.addEventListener('change', doPreview); el.addEventListener('keyup', doPreview);
      });

      // 清空
      clearBtn.addEventListener('click', () => {
        csvEl.value = ''; tableWrap.innerHTML = '<div style="padding:16px;color:var(--muted)">已清空，粘贴或上传后自动预览</div>';
        meta.textContent = '0 行 · 0 列'; time.textContent = '解析 0 ms'; status.textContent = '已清空';
      });

      // ===== 渲染 + 列宽自适应 =====
      function renderTable(rows, header) {
        if (!rows || !rows.length) { tableWrap.innerHTML = '<div style="padding:16px;color:var(--muted)">没有行可显示</div>'; return; }
        const maxRowsRender = 10000;
        const data = rows.slice(0, maxRowsRender);
        const cols = data[0].length;

        const table = document.createElement('table');
        const colgroup = document.createElement('colgroup');

        // —— 列宽自适应（测量表头 + 前 400 行内容） ——
        const widths = autoFitWidths(data, header, 400);
        for (let i = 0; i < cols; i++) {
          const col = document.createElement('col');
          col.style.width = widths[i] + 'px';
          colgroup.appendChild(col);
        }
        table.appendChild(colgroup);

        const thead = document.createElement('thead');
        const tbody = document.createElement('tbody');

        const headRow = document.createElement('tr');
        const first = data[0] || [];
        for (let i = 0; i < first.length; i++) {
          const th = document.createElement('th');
          th.textContent = header ? String(first[i] ?? '') : `列 ${i + 1}`;
          headRow.appendChild(th);
        }
        thead.appendChild(headRow);

        for (let r = (header ? 1 : 0); r < data.length; r++) {
          const tr = document.createElement('tr');
          const row = data[r];
          for (let c = 0; c < cols; c++) {
            const td = document.createElement('td');
            const v = row?.[c] ?? '';
            td.title = String(v);           // hover 可见完整内容
            td.textContent = v;
            tr.appendChild(td);
          }
          tbody.appendChild(tr);
        }

        table.appendChild(thead); table.appendChild(tbody);
        tableWrap.innerHTML = '';
        tableWrap.appendChild(table);
        // 大表格可横向滚动：table 宽度 >= 100%，容器 #tableWrap overflow:auto
      }

      // 计算列宽：测量像素 + 夹取区间（min/max）
      function autoFitWidths(data, header, sampleN = 400) {
        const cols = data[0]?.length || 0;
        const ctx = document.createElement('canvas').getContext('2d');
        ctx.font = '13px ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, "PingFang SC", "Microsoft YaHei"';
        const padd = 20;                      // 左右 padding 合计
        const minW = 80, maxW = 360;          // 列宽约束
        const widths = new Array(cols).fill(minW);

        const sampleRows = data.slice(0, Math.min(sampleN, data.length));
        for (let c = 0; c < cols; c++) {
          let w = 0;
          // 表头优先
          const head = header ? String(data[0][c] ?? '') : `列 ${c + 1}`;
          w = Math.max(w, Math.ceil(ctx.measureText(head).width) + padd);
          // 内容采样
          for (let r = (header ? 1 : 0); r < sampleRows.length; r++) {
            const txt = String(sampleRows[r]?.[c] ?? '');
            if (!txt) continue;
            w = Math.max(w, Math.ceil(ctx.measureText(txt).width) + padd);
            if (w >= maxW) break; // 已达上限可提前结束
          }
          widths[c] = Math.min(Math.max(w, minW), maxW);
        }
        return widths;
      }

      const inputAreaBox = document.getElementById('text-input-area-csv')
      const largeBtn = document.getElementById('text-large-btn')
      const minBtn = document.getElementById('text-min-btn')
      largeBtn.addEventListener('click', () => {
        inputAreaBox.classList.add('top-large-csv-text')
        // 显示 minBtn
        minBtn.style.display = 'block';
      })
      minBtn.addEventListener('click', () => {
        inputAreaBox.classList.remove('top-large-csv-text')
        minBtn.style.display = 'none';
      })









      // 读取文件并解析
      async function handleFile(path) {
        try {
          const content = await window.__TAURI__.fs.readTextFile(path);
          readFile(content)
        } catch (err) {
          alert('读取文件失败: ' + err.message);
          console.error(err);
        }
      }
// 获取 URL 参数（用于系统打开文件：tauri://open?path=xxx）
      function getQueryParam(key) {
        const urlParams = new URLSearchParams(window.location.search);
        return urlParams.get(key);
      }
      function getQueryParams() {
        const params = new URLSearchParams(window.location.search)
        return {
          path: params.get('path')
        }
      }

      // 页面加载时检查是否有通过系统打开的文件
      window.addEventListener('DOMContentLoaded', () => {
        const path = getQueryParam('path');
        if (path) {
          handleFile(path);
        }
      });
    })();
  </script>
</body>

</html>